package triannon.client.sprites;

import java.awt.Point;
import java.awt.Rectangle;
import java.util.Iterator;

import org.newdawn.slick.util.pathfinding.AStarPathFinder;
import org.newdawn.slick.util.pathfinding.Mover;
import org.newdawn.slick.util.pathfinding.PathFinder;
import org.newdawn.slick.util.pathfinding.TileBasedMap;

import triannon.client.util.TMath;
import triannon.client.util.ThreeDimensionalArrayIterator;

public class Field implements Iterable<ISprite>, TileBasedMap {
	private final ISprite[][][] field;
	private final boolean[][] blocks;
	private final boolean[][] visited;

	private final PathFinder finder;

	private final ThreeDimensionalArrayIterator<ISprite> iterator;

	public Field(int mapWidth, int mapHeight) {
		field = new ISprite[mapWidth][mapHeight][SpriteType.values().length];
		blocks = new boolean[mapWidth][mapHeight];
		visited = new boolean[mapWidth][mapHeight];

		clearVisited();

		iterator = new ThreeDimensionalArrayIterator<ISprite>(field);

		// init with null values
		for (int x = 0; x < mapWidth; x++) {
			for (int y = 0; y < mapHeight; y++) {
				for (int layer = 0; layer < SpriteType.values().length; layer++) {
					field[x][y][layer] = null;
				}
				blocks[x][y] = false;
			}
		}

		finder = new AStarPathFinder(this, 500, true);
	}

	public ISprite[] getSpritesAtPoint(Point p) {
		int x = Math.min(field.length - 1, TMath.toLevelCoorninates(p.x));
		int y = Math.min(field[0].length - 1, TMath.toLevelCoorninates(p.y));

		return field[x][y];
	}

	public void add(ISprite sprite) {
		((AbstractSprite) sprite).setField(this);

		int x = TMath.toLevelCoorninates(sprite.getX());
		int y = TMath.toLevelCoorninates(sprite.getY());

		int width = TMath.toLevelCoorninates(sprite.getWidth());
		int height = TMath.toLevelCoorninates(sprite.getHeight());

		// add bigger objects to multiple sectors
		for (int iX = 0; iX < width; iX++) {
			for (int iY = 0; iY < height; iY++) {
				field[x + iX][y + iY][sprite.getSpriteType().getLayer()] = sprite;

				blocks[x + iX][y + iY] = blocks[x + iX][y + iY] || sprite.isBlocker();
			}
		}

		if (sprite.isHigh()) {
			System.err.println(sprite + " is high");
			field[x][y - 1][SpriteType.BUILDING_HIGH.getLayer()] = sprite;
		}
	}

	public boolean moveSprite(int oldX, int oldY, int newX, int newY, int layer) {
		ISprite sprite = field[oldX][oldY][layer];
		boolean isFieldFree = field[newX][newY][layer] == null;

		if (isFieldFree) {
			field[newX][newY][layer] = sprite;
			field[oldX][oldY][layer] = null;

			if (field[newX][newY][layer].isBlocker()) {
				boolean block = false;
				for (int l = SpriteType.ENVIRONMENT.getLayer(); l < SpriteType.BUILDING_HIGH
						.getLayer(); l++) {
					block = block
							|| (field[oldX][oldY][l] != null && field[oldX][oldY][l]
									.isBlocker());
				}
				blocks[oldX][oldY] = block;
				blocks[newX][newY] = true;
			}
		}

		if (sprite instanceof LivingSprite) {
			LivingSprite lSprite = (LivingSprite) sprite;
			if (lSprite.getGoToX() > 0 && lSprite.getGoToY() > 0) {
				findPath(lSprite, TMath.toLevelCoorninates(lSprite.getGoToX()), TMath
						.toLevelCoorninates(lSprite.getGoToY()));
			}
		}

		return isFieldFree;
	}

	public void findPath(LivingSprite lSprite, int tx, int ty) {
		lSprite.setPath(finder.findPath(lSprite, lSprite.getLevelX(), lSprite
				.getLevelY(), tx, ty));
	}

	public Iterator<ISprite> iteratorForRect(Rectangle rect) {
		return new ThreeDimensionalArrayIterator<ISprite>(field, TMath
				.toLevelCoorninates(rect.x), TMath.toLevelCoorninates(rect.y), 0, TMath
				.toLevelCoorninates(rect.width) + 2, TMath
				.toLevelCoorninates(rect.height) + 2, SpriteType.values().length);

	}

	@Override
	public Iterator<ISprite> iterator() {
		iterator.reset();
		return iterator;
	}

	@Override
	public boolean blocked(Mover mover, int x, int y) {
		if (mover == null) {
			return blocks[x][y];
		}
		return blocks[x][y]
				&& mover != field[x][y][((ISprite) mover).getSpriteType().getLayer()];
	}

	@Override
	public float getCost(Mover mover, int sx, int sy, int tx, int ty) {
		if (sx != tx && sy != ty) {
			return 1.41f;
		}
		return 1;
	}

	@Override
	public int getHeightInTiles() {
		return blocks[0].length;
	}

	@Override
	public int getWidthInTiles() {
		return blocks.length;
	}

	public void clearVisited() {
		for (int x = 0; x < getWidthInTiles(); x++) {
			for (int y = 0; y < getHeightInTiles(); y++) {
				visited[x][y] = false;
			}
		}
	}

	@Override
	public void pathFinderVisited(int x, int y) {
		visited[x][y] = true;
	}

}
